/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Graphics
{
	public class FrameStats : DrawableGameComponent
	{
		private ContentManager content;
		private SpriteBatch spriteBatch;

		private TextLayout textLayout = new TextLayout();

		private ulong numFrames;

		private const int NumTimings = 30;

		private int updateTimeIdx;
		private TimeSpan[] updateTimes = new TimeSpan[NumTimings];
		private int drawTimeIdx;
		private TimeSpan[] drawTimes = new TimeSpan[NumTimings];

		private ulong numCollections, framesAtLastCollection, framesBetweenLastTwoCollections;
		private WeakReference gcFlag = new WeakReference( new object() );

		private readonly TimeSpan oneSecond = TimeSpan.FromSeconds( 1 );

		private TextBuffer customText = new TextBuffer( 1024 );
		private TextBuffer textBuffer = new TextBuffer( 1024 + 256 );

		public FrameStats( Game game )
			: base( game )
		{
			content = new ContentManager( game.Services, "Content\\Cobalt.Framework" );

			DrawOrder = int.MaxValue;

			textLayout.FormattingHandler = new TextFormatter();
			textLayout.ClippingMode = TextClippingMode.None;
			textLayout.TextSize = 12;
		}

		public TextBuffer CustomText
		{
			get { return customText; }
		}

		protected override void LoadContent()
		{
			spriteBatch = new SpriteBatch( GraphicsDevice );
			textLayout.Font = content.Load<Font>( "DefaultFont" );
		}

		protected override void UnloadContent()
		{
			Helpers.DisposeAndNull( ref spriteBatch );

			content.Unload();	
		}
		
		private TimeSpan Average( TimeSpan[] times )
		{
			TimeSpan ret = times[0];
			for( int i = 1; i < times.Length; i++ )
				ret += times[i];

			return new TimeSpan( ret.Ticks / times.Length );
		}

		public override void Update( GameTime gameTime )
		{
			updateTimes[updateTimeIdx] = gameTime.ElapsedGameTime;
			updateTimeIdx = (updateTimeIdx + 1) % updateTimes.Length;

			if( !gcFlag.IsAlive )
			{
				numCollections++;

				framesBetweenLastTwoCollections = numFrames - framesAtLastCollection;
				framesAtLastCollection = numFrames;

				gcFlag = new WeakReference( new object() );
			}
		}

		private string GetColorSequenceForTime( TimeSpan time, TimeSpan target )
		{
			target += TimeSpan.FromMilliseconds( 0.5 );
			TimeSpan closeTime = target - TimeSpan.FromMilliseconds( 2 );

			if( time > target )
				return "\f0"; //red

			if( closeTime > TimeSpan.Zero && time > closeTime )
				return "\f1"; //orange

			return "\f3"; //green
		}

		public override void Draw( GameTime gameTime )
		{
			numFrames++;

			drawTimes[drawTimeIdx] = gameTime.ElapsedRealTime;
			drawTimeIdx = (drawTimeIdx + 1) % drawTimes.Length;

			textBuffer.Clear();
			if( Game.IsFixedTimeStep )
			{
				TimeSpan avgUpdateTime = Average( updateTimes );
				TimeSpan avgDrawTime = Average( drawTimes );

				textBuffer.AppendText( GetColorSequenceForTime( avgUpdateTime, Game.TargetElapsedTime ) );
				textBuffer.AppendFloat( (float)avgUpdateTime.TotalMilliseconds, 2, ' ', 2 );
				textBuffer.AppendText( "\f-ms/update, target: " );
				textBuffer.AppendFloat( (float)Game.TargetElapsedTime.TotalMilliseconds, 2, ' ', 2 );
				textBuffer.AppendText( "ms/frame, hitting: " );
				textBuffer.AppendText( GetColorSequenceForTime( avgDrawTime, Game.TargetElapsedTime ) );
				textBuffer.AppendFloat( (float)avgDrawTime.TotalMilliseconds, 2, ' ', 2 );
				textBuffer.AppendText( "\f-ms/frame" );
			}
			else
			{
				TimeSpan avgDrawTime = Average( drawTimes );

				textBuffer.AppendText( GetColorSequenceForTime( avgDrawTime, Game.TargetElapsedTime ) );
				textBuffer.AppendFloat( (float)avgDrawTime.TotalMilliseconds, 2, ' ', 2 );
				textBuffer.AppendText( "\f-ms/frame" );
			}

			textBuffer.AppendText( "\n" );

			textBuffer.AppendInt( (int)numCollections, 6, ' ' );
			textBuffer.AppendText( " garbage collections\n  " );
			textBuffer.AppendInt( (int)framesBetweenLastTwoCollections, 6, ' ' );
			textBuffer.AppendText( " frames between last two collections" );

			if( customText.Length > 0 )
			{
				textBuffer.AppendText( "\n" );
				textBuffer.AppendText( customText );
			}

			spriteBatch.Begin( SpriteBlendMode.AlphaBlend,
				SpriteSortMode.Deferred, SaveStateMode.SaveState );

			Rectangle safeArea = GraphicsDevice.Viewport.TitleSafeArea;

			textLayout.Reset();
			textLayout.LayoutLines( textBuffer, new Point( safeArea.X + 8, safeArea.Y + 8 ), Vector2.Zero );

			textLayout.BeginGlyphLayout();
			textLayout.Draw( spriteBatch, Color.Black );

			textLayout.BeginGlyphLayout();
			textLayout.Draw( spriteBatch );

			spriteBatch.End();
		}
	}
}